﻿using System;
using System.IO;
using System.Threading;
using System.Configuration;
using System.Net;
using System.Net.Sockets;
using System.Reflection;

namespace SBPweb.Forge.Web
{
    
    public class PolicySocketServer
    {

        #region Private fields

        private const string policyRequestString = "<policy-file-request/>";

        private static ManualResetEvent tcpClientConnected = new ManualResetEvent(false);

        private TcpListener listener = null;
        private TcpClient client = null;
        private int receivedLength = 0;
        private byte[] policy = null;
        private byte[] receiveBuffer = null;
        private int port = 943;
        private Thread listenThread;
        private IPAddress ipAddress;

        #endregion

        #region Constructor logic

        public PolicySocketServer()
        {
        }

        public PolicySocketServer(IPAddress ip, int port)
        {
            this.ipAddress = ip;
            this.port = port;
        }

        #endregion

        #region Events

        public event EventHandler<ClientActionEventArgs> ClientRequest;

        public event EventHandler<ClientActionEventArgs> ClientResponse;

        #endregion

        #region Methods

        public void Start()
        {
            InitializeData();
            try
            {
                //Using TcpListener which is a wrapper around a Socket
                //Allowed port is 943 for Silverlight sockets policy data
                listener = new TcpListener(ipAddress, port);
                listener.Start();
                Console.WriteLine("Policy server listening...");
                while (true)
                {
                    tcpClientConnected.Reset();
                    Console.WriteLine("Waiting for client connection...");
                    listener.BeginAcceptTcpClient(new AsyncCallback(OnBeginAccept), null);
                    tcpClientConnected.WaitOne(); //Block until client connects
                }
            }
            catch (Exception exp)
            {
                LogError(exp);
            }
        }

        public void StartAsync()
        {
            ThreadStart tS = new ThreadStart(Start);
            listenThread = new Thread(tS);
            listenThread.Start();
        }

        public void Stop()
        {
            listener.Stop();
        }

        private void InitializeData()
        {
            string policyFile = ConfigurationManager.AppSettings["policyFilePath"];
            using (FileStream fs = new FileStream(AppDomain.CurrentDomain.BaseDirectory + policyFile, FileMode.Open, FileAccess.Read))
            {
                policy = new byte[fs.Length];
                fs.Read(policy, 0, policy.Length);
            }
            receiveBuffer = new byte[policyRequestString.Length];
        }

        private void OnBeginAccept(IAsyncResult ar)
        {
            client = listener.EndAcceptTcpClient(ar);
            client.Client.BeginReceive(receiveBuffer, 0, policyRequestString.Length, SocketFlags.None, new AsyncCallback(OnReceiveComplete), null);
        }

        private void OnReceiveComplete(IAsyncResult ar)
        {
            try
            {
                receivedLength += client.Client.EndReceive(ar);
                //See if there's more data that we need to grab
                if (receivedLength < policyRequestString.Length)
                {
                    //Need to grab more data so receive remaining data
                    client.Client.BeginReceive(receiveBuffer, receivedLength, policyRequestString.Length - receivedLength, SocketFlags.None, new AsyncCallback(OnReceiveComplete), null);
                    return;
                }
                //Check that <policy-file-request/> was sent from client
                string request = System.Text.Encoding.UTF8.GetString(receiveBuffer, 0, receivedLength);

                OnClientRequest(client.Client.RemoteEndPoint, request);

                if (StringComparer.InvariantCultureIgnoreCase.Compare(request, policyRequestString) != 0)
                {
                    //Data received isn't valid so close
                    client.Client.Close();
                    return;
                }
                //Valid request received....send policy data
                client.Client.BeginSend(policy, 0, policy.Length, SocketFlags.None, new AsyncCallback(OnSendComplete), null);
            }
            catch (Exception exp)
            {
                client.Client.Close();
                LogError(exp);
            }
            receivedLength = 0;
            tcpClientConnected.Set(); //Allow waiting thread to proceed
        }

        private void OnSendComplete(IAsyncResult ar)
        {
            try
            {
                SocketError sE = SocketError.Success;
                client.Client.EndSend(ar, out sE);

                OnClientResponse(client.Client.RemoteEndPoint, System.Text.Encoding.Default.GetString(policy));

            }
            catch (Exception exp)
            {
                LogError(exp);
            }
            finally
            {
                //Close client socket
                client.Client.Close();
            }
        }

        private void OnClientRequest(EndPoint clientEndPoint, string request)
        {
            if (ClientRequest != null)
            {
                ClientRequest(this, new ClientActionEventArgs(clientEndPoint, request));
            }
        }

        private void OnClientResponse(EndPoint clientEndPoint, string response)
        {
            if (ClientResponse != null)
            {
                ClientResponse(this, new ClientActionEventArgs(clientEndPoint, response));
            }
        }

        private void LogError(Exception exp)
        {
            string appFullPath = Assembly.GetCallingAssembly().Location;
            string logPath = appFullPath.Substring(0, appFullPath.LastIndexOf("\\")) + ".log";
            StreamWriter writer = new StreamWriter(logPath, true);
            try
            {
                writer.WriteLine(logPath, String.Format("Error in PolicySocketServer: {0} \r\n StackTrace: {1}", exp.Message, exp.StackTrace));
            }
            catch
            { 
            }
            finally
            {
                writer.Close();
            }
        }

        #endregion

        #region Subclassess

        public class ClientActionEventArgs : EventArgs
        {

            #region Constructor logic

            public ClientActionEventArgs(EndPoint clientEndPoint, string request)
            {
                Request = request;
                ClientEndPoint = clientEndPoint;
            }

            #endregion

            #region Property accessors

            public string Request
            {
                get;
                protected set;
            }

            public EndPoint ClientEndPoint
            {
                get;
                protected set;
            }
            #endregion

        }

        #endregion

    }

}